{
 "cells": [
  {
   "cell_type": "markdown",
   "source": [
    "# FLASK 介绍\r\n",
    "\r\n",
    "FLASK 是 Armin Ronacher 在2010年写的 Python 的微框架。微是什么意思？\r\n",
    "\r\n",
    "微意味着 `FLASK` 很小。它没有其他流行的 Python 框架如 Django 或 Pyramid 提供的特定工具集或库。`FLASK` 的设计考虑了延展性。它只提供一组核心特性，其余的工作依赖于扩展。开箱即用. Flask 不支持访问数据库、表单验证、身份验证、文件上传等功能。要将这些函数添加到应用程序中，必须使用扩展。因为您可以选择您想要的扩展。你最终会得到精简的堆栈，而没有任何额外的麻烦。\r\n",
    "\r\n",
    "FLASK 对于如何构建应用程序也没有那么严格。不像 Django 这样的框架，你必须遵循严格的规则。在 Flask 中，您可以自由地按照自己想要的方式构建应用程序。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "# 如何安装FLASK\r\n",
    "\r\n",
    "请确保您的系统上已经安装了工作的 Python 安装和 virtualenv 包。要学习如何安装 Python 和 virtualenv ，请点击这里。\r\n",
    "\r\n",
    "## Creating Virtual Environment 创建虚拟环境(可跳过)\r\n",
    "\r\n",
    "虚拟环境是 Python 安装的独立副本，在这里我们可以安装包而不影响全局的 Python 安装。创建一个名为 flask_app 的新目录。此目录将托管我们的 Flask 应用程序。\r\n",
    "\r\n",
    "```\r\n",
    "overiq@vm:~$ mkdir flask_app\r\n",
    "overiq@vm:~$ cd flask_app/\r\n",
    "overiq@vm:~/flask_app$ virtualenv env\r\n",
    "Using base prefix '/usr'\r\n",
    "New python executable in /home/overiq/flask_app/env/bin/python3\r\n",
    "Also creating executable in /home/overiq/flask_app/env/bin/python\r\n",
    "Installing setuptools, pip, wheel...done.\r\n",
    "overiq@-vm:~/flask_app$ source env/bin/activate\r\n",
    "#Windows 用户可以通过输入以下命令来激活虚拟环境。\r\n",
    "C:\\Users\\overiq\\flask_app>env\\Scripts\\activate\r\n",
    "(env) C:\\Users\\overiq\\flask_app>\r\n",
    "```\r\n",
    "注意提示字符串 i.e (env)前的括号中的虚拟环境名称。这表明我们的虚拟环境已经启动并正在运行。从现在开始安装的软件包只能在这个虚拟环境中使用。\r\n",
    "激活虚拟环境会暂时改变 PATH 环境变量。因此，如果您在终端中键入 Python，那么驻留在虚拟环境 env 目录中的 Python 解释器将被调用，而不是全局 Python 解释器。\r\n",
    "一旦你完成了与虚拟环境的工作，你必须使用 deactivate 命令停用它。`(env) overiq@vm:~/flask_app$ deactivate` 此命令使全局 Python 解释器再次可用。\r\n",
    "\r\n",
    "## 安装flask\r\n",
    "要在虚拟环境中安装 Flask，请输入以下命令。\r\n",
    "pip install flask\r\n",
    "您可以通过调用 Python 解释器和导入 Flask 来验证安装是否成功。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "source": [
    "import flask\r\n",
    "flask.__version__"
   ],
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "'1.1.4'"
      ]
     },
     "metadata": {},
     "execution_count": 1
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "# Flask 基础知识"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Hello World in Flask\r\n",
    "让我们通过创建一个输出“ Hello World”的简单应用程序来开始我们进入 Flask 的尝试。创建一个名为 main.py 的新文件，并在其中输入以下代码。\r\n",
    "\r\n",
    "```python\r\n",
    "from flask import Flask\r\n",
    "app = Flask(__name__)\r\n",
    "\r\n",
    "@app.route('/')\r\n",
    "def index():\r\n",
    "    return 'Hello World'\r\n",
    "\r\n",
    "if __name__ == \"__main__\":\r\n",
    "    app.run()\r\n",
    "```    \r\n",
    "\r\n",
    "我们刚刚在 Flask 中创建了“ Hello World”应用程序。如果 main.py 中的代码对您来说没有意义，那也没关系。在接下来的部分中，我们将更详细地讨论所有内容。要运行 main.py，请在虚拟环境中输入以下命令。\r\n",
    "`python main.py`\r\n",
    "\r\n",
    "运行 main.py 文件将启动端口5000上由 flask 提供的开发服务器。现在打开你最喜欢的浏览器，访问 http://127.0.0.1:5000/，看看 Hello World 应用程序的运行情况。\r\n",
    "\r\n",
    "若要停止服务器，请按 ctrl + c。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 实例化 Flask 应用\r\n",
    "\r\n",
    "每个flask应用程序必须有一个flask类的实例。该实例实际上是一个 WSGI (Web服务器网关接口)应用程序，这仅仅意味着 web 服务器将它接收到的所有请求传递给该实例进行进一步处理。我们实例化一个类 Flask 的对象如下:\r\n",
    "\r\n",
    "```\r\n",
    "from flask import Flask\r\n",
    "app = Flask(__name__)\r\n",
    "```\r\n",
    "\r\n",
    "在第1行中，我们从flask包装中导入一个名为 Flask 的类。\r\n",
    "\r\n",
    "在第2行中，我们通过传递 `__name__` argument 给 Flask 构造函数来实例化一个 `Flask` 对象。`Flask` 构造函数有一个必需的参数，这个参数就是 `application` 包的名称。大多数时候 `__name__` 是正确的值。Flask 使用应用程序包的名称来查找静态资产、模板等。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 创建路由 \r\n",
    "路由 `route` 是将 URL 绑定到视图函数的行为。视图函数只是一个响应请求的函数。在 Flask 中，我们使用由 `Flask` 实例(即app变量)提供的 `route` 装饰器(@开头的变量为装饰器名) 将 URL 关联到视图函数。下面是我们如何在 Flask 中创建一条路由。\r\n",
    "```\r\n",
    "@app.route('/')\r\n",
    "def index():\r\n",
    "    return 'Hello World'\r\n",
    "```\r\n",
    "此代码将 index() 视图函数 注册为应用程序 根URL(\"/\") 的处理程序。换句话说，每当应用程序接收到一个请求，其请求的路径是\"/\"时, index() 函数将被调用来完成请求。\r\n",
    "\r\n",
    "## 替代方法，您可以使用 add_url_rule() 方法\r\n",
    "```\r\n",
    "def index():\r\n",
    "    return 'Hello World'\r\n",
    "app.add_url_rule('/', 'index', index)\r\n",
    "```\r\n",
    "视图函数**必须返回一个字符串**。尝试返回其他内容将导致500的内部服务器错误。\r\n",
    "\r\n",
    "## 当路由中的 URL 以斜线(/)结尾时\r\n",
    "\r\n",
    "Flask 将把请求重定向到末尾带斜线的 URL。这意味着对 /career 的要求将被转向 /career/。\r\n",
    "\r\n",
    "```\r\n",
    "@app.route('/career/')\r\n",
    "def career():\r\n",
    "    return 'Career Page'\r\n",
    "```\r\n",
    "\r\n",
    "## 我们还可以将多个 url 映射到同一个视图函数\r\n",
    "\r\n",
    "```\r\n",
    "@app.route('/contact/')\r\n",
    "@app.route('/feedback/')\r\n",
    "def feedback():\r\n",
    "    return 'Feedback Page'\r\n",
    "```\r\n",
    "\r\n",
    "## 如果你试图访问一个没有映射到视图函数的 URL，你会得到一个404 Not Found 错误。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 动态 URL \r\n",
    "\r\n",
    "动态 URL 是由一个或多个影响页面输出的可变部分组成的 URL。\r\n",
    "\r\n",
    "我们可以将 URL 中的动态部分标记为 <variable_name> 。然后将动态部分作为关键字参数传递给 view 函数。下面的清单定义了一个包含动态部分的路由。\r\n",
    "\r\n",
    "```\r\n",
    "@app.route('/user/<id>/')\r\n",
    "def user_profile(id):\r\n",
    "    return \"Profile page of user #{}\".format(id)\r\n",
    "```\r\n",
    "\r\n",
    "## 转换器 `converter`\r\n",
    "默认情况下，URL 的动态部分作为字符串传递给视图函数。我们可以改变这一点,通过在 URL 的 动态部分之前指定一个转换器作为 `<converter:variable_name>` 来实现。\r\n",
    "例如,`/user/<int:id>/`route 将匹配如/user/1/、/user/2000/的URL。它将不匹配如 `/user/cowboy/`, /user/foobar10/ 和 `/user/@@##/` 的url。\r\n",
    "\r\n",
    "```\r\n",
    "@app.route('/user/<int:user_id>/')\r\n",
    "def user_profile(user_id):\r\n",
    "    return \"Profile page of user #{}\".format(user_id)\r\n",
    "```\r\n",
    "\r\n",
    "| Converter | Description                                          |\r\n",
    "| :-------- | :--------------------------------------------------- |\r\n",
    "| `string`  | Accepts any string, this is the default.             |\r\n",
    "| `int`     | Accepts integers.                                    |\r\n",
    "| `float`   | Accepts floating pointing values.                    |\r\n",
    "| `path`    | Accepts path names with leading and forward slashes. |\r\n",
    "| `uuid`    | Accepts uuid strings.                                |\r\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 启动服务器\r\n",
    "要启动开发服务器，我们调用 Flask 对象的 run ()方法。\r\n",
    "\r\n",
    "```\r\n",
    "if __name__ == \"__main__\":\r\n",
    "    app.run()\r\n",
    "```\r\n",
    "\r\n",
    "## 调试模式\r\n",
    "要打开 debug 模式，请将 debug = True 传递给 run ()方法，如下所示:\r\n",
    "```\r\n",
    "if __name__ == \"__main__\":\r\n",
    "    app.run(debug=True)\r\n",
    "```\r\n",
    "或者将 Flask 实例的调试属性设置为 True  \r\n",
    "```\r\n",
    "from flask import Flask\r\n",
    "app = Flask(__name__)\r\n",
    "app.debug = True\r\n",
    "```"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## FLASK如何处理一个请求？\r\n",
    "\r\n",
    "那么，当 Flask 接收到客户端的请求时，它如何知道要执行哪个视图函数？\r\n",
    "\r\n",
    "FLASK 内部维护了要执行的 url 和视图函数的映射。这个映射是使用 Flask 实例的 `route` 装饰器 或 `add_url_rule()` 方法创建的。我们可以使用 Flask 实例的 `url_map` 属性来访问这个映射。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "source": [
    "from main2 import app\r\n",
    "app.url_map"
   ],
   "outputs": [],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "# FLASK 中的语境\r\n",
    "\r\n",
    "在 Flask 中，我们使用 request 对象访问传入请求中的数据，\r\n",
    "\r\n",
    "如下所示:\r\n",
    "\r\n",
    "```\r\n",
    "from flask import Flask, request\r\n",
    "\r\n",
    "@app.route('/')\r\n",
    "def requestdata():\r\n",
    "    return \"Hello! Your IP is {} and you are using {}: \".format(request.remote_addr, request.user_agent)\r\n",
    "```\r\n",
    "\r\n",
    "上面的代码可能会给您一个印象，即 `request` 是一个全局对象，实际上它不是。如果 `request` 是一个全局对象，那么在一个多线程程序中，我们的应用程序将无法区分两个同时发出的请求，因为一个多线程程序共享线程中的所有变量。使用一种叫做 `Contexts` 的东西来使某些变量像全局变量一样工作，当你访问它们的时候，你就可以访问当前线程的对象。在术语集中，这样的变量被称为  `thread-locals` 线程局部变量。\r\n",
    "\r\n",
    "Flask 提供了两种 `Context` 上下文语境:\r\n",
    "\r\n",
    "Application Context. 应用程序上下文\r\n",
    "Request Context. 请求上下文\r\n",
    "\r\n",
    "应用程序上下文用于存储与应用程序通用的值，如数据库连接、配置等; 而请求上下文用于存储特定于每个请求的值。\r\n",
    "\r\n",
    "The application context exposes objects like current_app and g. The current_app refers to the instance handling the request and g is used to temporarily store data during request handling. Once a value is set you can access it inside any view function. The data stored in g resets after every request.\r\n",
    "\r\n",
    "应用程序上下文公开像 `current_app` 和 `g` 这样的对象。 `current_app` 指向处理请求的实例，而`g` 用于在请求处理期间临时存储数据。一旦设置了一个值，您就可以在任何视图函数中访问它。存储在 `g` 中的数据在每次请求后重置。\r\n",
    "\r\n",
    "Just like the application context, request context also exposes objects like request and session. As already discussed, the request object contains the information about the current web request and session is a dictionary-like object that is used to store values that persists between requests.\r\n",
    "\r\n",
    "与应用程序上下文一样，请求上下文也公开 `request` 和 `session` 等对象。如前所述， `request` 对象包含有关当前 web 请求的信息， `session` 是一个类似字典的对象，用于存储在请求之间持久存储的值。\r\n",
    "\r\n",
    "如果您尝试在 视图函数 `a view function` 之外或在 `pythonshell` 中访问这些对象，则会出现错误。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "source": [
    "from flask import Flask, request, current_app\r\n",
    "request.method "
   ],
   "outputs": [],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "`request.method` 返回请求中使用的 HTTP 方法，但是由于没有实际的 HTTP 请求，请求上下文是未激活的。\r\n",
    "\r\n",
    "我们可以使用 Flask 实例的 `app_context()` 方法创建应用程序上下文。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "source": [
    "from main2 import app\r\n",
    "from flask import Flask, request, current_app\r\n",
    "\r\n",
    "app_context = app.app_context()\r\n",
    "app_context.push()\r\n",
    "\r\n",
    "current_app.name"
   ],
   "outputs": [],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "source": [
    "代码可以使用 with 语句简化如下(With 语句是创建上下文的首选方法):"
   ],
   "outputs": [],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "source": [
    "from main2 import app\r\n",
    "from flask import Flask, request, current_app\r\n",
    "\r\n",
    "with app.app_context():\r\n",
    "    print(current_app.name)"
   ],
   "outputs": [],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "类似地，我们可以使用 Flask 实例的 `test_request_context()`方法创建请求上下文。需要记住的重要一点是，每当推送请求上下文时，应用程序上下文即被创建(如果尚未存在的话)。下面的 shell 会话演示了如何创建请求上下文:"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "source": [
    "from main2 import app\r\n",
    "from flask import Flask, request, current_app\r\n",
    "\r\n",
    "with app.test_request_context('/products'):  # The URL /products is arbitrarily chosen 任意选择的.\r\n",
    "    print(request.path)\r\n",
    "    print(request.method)\r\n",
    "    print(current_app.name)"
   ],
   "outputs": [],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "# Custom Response and Hook Points in Flask 在Flask中定制响应和挂钩点"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 1. 用字符串创建响应\r\n",
    "```\r\n",
    "@app.route('/books/<genre>')\r\n",
    "def books(genre):\r\n",
    "    return \"All Books in {} category\".format(genre)\r\n",
    "```\r\n",
    "当 Flask 看到我们从视图函数返回一个字符串时，它会自动将字符串转换成一个响应对象(使用 make_response() 方法) ，其中字符串作为响应的主体，HTTP状态码为200，内容类型的头部设置为 text/html。    "
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 2. 用 make_response()创建响应\r\n",
    "\r\n",
    "make_response()的语法如下:\r\n",
    "`res_obj = make_response(res_body, status_code=200)`\r\n",
    "`res_body` 是表示响应主体的必需参数， `status_code` 状态代码是可选的 HTTP 状态码，默认值为200。\r\n",
    "\r\n",
    "下面的清单显示了如何使用 make _ response ()函数设置附加标头。\r\n",
    "```python\r\n",
    "from flask import Flask, make_response, \r\n",
    "\r\n",
    "@app.route('/books/<genre>')\r\n",
    "def books(genre):\r\n",
    "    res = make_response(\"All Books in {} category\".format(genre))\r\n",
    "    res.headers['Content-Type'] = 'text/plain'\r\n",
    "    res.headers['Server'] = 'Foobar'\r\n",
    "    return res\r\n",
    "```\r\n",
    "\r\n",
    "下面的清单显示了如何使用 make _ response ()函数返回 HTTP 404错误。\r\n",
    "```python\r\n",
    "@app.route('/')\r\n",
    "def http_404_handler():\r\n",
    "    return make_response(\"<h2>404 Error</h2>\", 400)\r\n",
    "```\r\n",
    "\r\n",
    "下面的清单在客户端浏览器中设置了两个 cookie。 第三个参数来设置 cookie 的过期时间(15天)\r\n",
    "```python\r\n",
    "@app.route('/cookie')\r\n",
    "def set_cookie():\r\n",
    "    res = make_response(\"Cookie setter\")\r\n",
    "    res.set_cookie(\"favorite-color\", \"skyblue\", 60*60*24*15)\r\n",
    "    res.set_cookie(\"favorite-font\", \"sans-serif\", 60*60*24*15)\r\n",
    "    return res\r\n",
    "```\r\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 3. 使用元组创建响应\r\n",
    "使用元组，格式如下:\r\n",
    "```\r\n",
    "(response, status, headers) \r\n",
    "(response, headers) \r\n",
    "(response, status)\r\n",
    "```\r\n",
    "`response` 响应是一个字符串，表示响应的主体， `status` 状态是 HTTP状态码，它可以是整数或字符串， `headers` 头是一个包含头值的字典。\r\n",
    "\r\n",
    "此视图函数将返回 HTTP 500内部服务器错误。\r\n",
    "```python\r\n",
    "@app.route('/')\r\n",
    "def http_500_handler():\r\n",
    "    return (\"<h2>500 Error</h2>\", 500)\r\n",
    "```\r\n",
    "因为我们可以在创建元组时省略括号，所以上面的代码也可以这样写:\r\n",
    "```python\r\n",
    "@app.route('/')\r\n",
    "def http_500_handler():\r\n",
    "    return \"<h2>500 Error</h2>\", 500\r\n",
    "```\r\n",
    "下面的清单展示了如何使用元组设置头文件:\r\n",
    "```python\r\n",
    "@app.route('/')\r\n",
    "def render_markdown():\r\n",
    "    return \"## Heading\", 200, {'Content-Type': 'text/markdown'}\r\n",
    "```\r\n",
    "\r\n",
    "下面的清单使用 HTTP 302响应代码(临时重定向)将用户重定向到 HTTP://localhost:5000/login\r\n",
    "```python\r\n",
    "@app.route('/transfer')\r\n",
    "def transfer():\r\n",
    "    return \"\", 302, {'location': 'http://localhost:5000/login'}\r\n",
    "```\r\n",
    "\r\n",
    "将用户重定向到不同的页面是很常见的，所以 Flask 提供了一个名为 `redirect()` 的辅助函数(helper function)，以简化这项工作。\r\n",
    "```python\r\n",
    "from flask import Flask, redirect\r\n",
    "\r\n",
    "@app.route('/transfer')\r\n",
    "def transfer():\r\n",
    "    return redirect(\"http://localhost:5000/login\")\r\n",
    "    # 默认情况下，redirect ()执行 HTTP 302重定向， 如果要执行 HTTP 301重定向，将301的 HTTP状态码传递给 redirect ()函数\r\n",
    "    # return redirect(\"http://localhost:5000/login\", code=301)\r\n",
    "```\r\n",
    "\r\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Hook Points 勾子点\r\n",
    "\r\n",
    "在 web 应用程序中，在每个请求之前执行一些代码是很常见的。例如，假设我们想记录访问我们应用程序的用户的 IP 地址，或者在显示隐藏页面之前对用户进行身份验证。与其在每个视图函数中复制这样的代码，Flask 为这样的场景提供了下面的装饰器 decorators :\r\n",
    "\r\n",
    "`before_first_request`: 这个 装饰器 注册一个函数，在处理第一个请求之前执行。\r\n",
    "\r\n",
    "`before_request`:  这个 装饰器 注册一个函数，在处理请求之前执行。\r\n",
    "\r\n",
    "`after_request`: : 这个 装饰器 注册一个函数，在处理请求之后执行。如果请求处理程序中发生未处理的异常，则不会调用已注册的函数。函数必须接受一个响应对象并返回相同或新的响应。\r\n",
    "\r\n",
    "`teardown_request`: 类似于 `after_request` 装饰器 ，但是注册的函数将始终执行，不管请求处理程序是否抛出异常。\r\n",
    "\r\n",
    "## flask_app/hooks.py\r\n",
    "\r\n",
    "```python\r\n",
    "from flask import Flask, request, g\r\n",
    "\r\n",
    "app = Flask(__name__)\r\n",
    "\r\n",
    "@app.before_first_request\r\n",
    "def before_first_request():\r\n",
    "    print(\"before_first_request() called\")\r\n",
    "\r\n",
    "@app.before_request\r\n",
    "def before_request():\r\n",
    "    print(\"before_request() called\")\r\n",
    "\r\n",
    "@app.after_request\r\n",
    "def after_request(response):\r\n",
    "    print(\"after_request() called\")\r\n",
    "    return response\r\n",
    "\r\n",
    "@app.route(\"/\")\r\n",
    "def index():\r\n",
    "    print(\"index() called\")\r\n",
    "    return '<p>Testings Request Hooks</p>'\r\n",
    "\r\n",
    "if __name__ == \"__main__\":\r\n",
    "    app.run(debug=True)\r\n",
    "```    "
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 使用 abort() 中止请求\r\n",
    "\r\n",
    "Flask 提供了一个名为 abort ()的函数，用于终止具有特定错误码(如404、500等)的请求。例如:\r\n",
    "\r\n",
    "```\r\n",
    "from flask import Flask, abort\r\n",
    "\r\n",
    "@app.route('/')\r\n",
    "def index():\r\n",
    "    abort(404)\r\n",
    "    # code after abort() execution will never be executed\r\n",
    "This view function will return a generic 404 page that looks like this:\r\n",
    "```\r\n",
    "\r\n",
    "这个视图函数将返回一个通用的404页面, Abort ()将为其他类型的错误显示类似的页面。如果要自定义错误页面，请使用下面将要讨论的 errorhandler 装饰器。\r\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Custom Error Pages 自定义错误页面\r\n",
    "\r\n",
    "`errorhandler` 装饰器用于创建自定义错误页面。它只接受一个参数，即为其创建自定义错误页面的 HTTP 错误代码。使用 `errorhandler` `decorator` 打开 hooks.py 文件并为 HTTP 404和 HTTP 500错误创建自定义错误页面，如下所示(突出显示更改) :\r\n",
    "\r\n",
    "请注意，两个错误处理程序都接受一个名为 error 的参数，该参数包含有关所发生错误类型的附加信息。\r\n",
    "\r\n",
    "```python\r\n",
    "## The errorhandler decorator\r\n",
    "@app.errorhandler(404)\r\n",
    "def http_404_handler(error):\r\n",
    "    return \"<p>HTTP 404 Error Encountered</p>\", 404\r\n",
    "\r\n",
    "@app.errorhandler(500)\r\n",
    "def http_500_handler(error):\r\n",
    "    return \"<p>HTTP 500 Error Encountered</p>\", 500\r\n",
    "\r\n",
    "@app.route(\"/test/\")\r\n",
    "def test():\r\n",
    "    # print(\"index() called\")\r\n",
    "    # return '<p>Testings Request Hooks</p>'\r\n",
    "    abort(404)\r\n",
    "```\r\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "# Flask 模板\r\n",
    "\r\n",
    "现在大多数网页都很长，并且由许多动态组件组成。我们使用模板，而不是在视图函数中嵌入大块的 HTML 字符串(维护起来会很糟糕)。\r\n",
    "\r\n",
    "`Templates` 模板\r\n",
    "\r\n",
    "模板只是一个文本文件，其中包含静态 HTML 代码以及一些特殊的标记，表示在请求时将知道的动态内容。取代动态标记并生成扁平 HTML 页面的过程称为模板呈现。\r\n",
    "\r\n",
    "Flask 与一个名为 `Jinja`  的模板引擎捆绑在一起，该引擎完成了解析模板并将其转换为扁平 HTML 页面的实际工作。\r\n",
    "\r\n",
    "`Jinja` 模板引擎是 Python 中最强大和最流行的模板引擎之一。如果你曾经使用过 Django 模板，那么你会有宾至如归的感觉。值得注意的是，`Jinja` 和 `Flask` 是两个独立的包，可以单独使用。\r\n",
    "\r\n",
    "##  使用 render_template() 渲染模板\r\n",
    "\r\n",
    "默认情况下，Flask 在应用程序文件夹中名为 `templates` 的子目录中查找模板。我们可以通过在创建应用程序实例时将 `template_folder` 参数传递给 Flask 构造函数来更改这种默认行为。\r\n",
    "\r\n",
    "`app = Flask(__name__, template_folder=\"jinja_templates\")`\r\n",
    "\r\n",
    "目前，我们没有任何理由这样做，因此我们将继续使用默认模板目录来存储模板。\r\n",
    "\r\n",
    "flask_app/main2.py\r\n",
    "```\r\n",
    "from flask import Flask, request, render_template\r\n",
    "app = Flask(__name__)\r\n",
    "\r\n",
    "@app.route('/')\r\n",
    "def index():\r\n",
    "    return render_template('index.html', name='Jerry')\r\n",
    "#...\r\n",
    "```"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "flask_app/templates/index.html\r\n",
    "```\r\n",
    "<!DOCTYPE html>\r\n",
    "<html lang=\"en\">\r\n",
    "<head>\r\n",
    "    <meta charset=\"UTF-8\">\r\n",
    "    <title>Title</title>\r\n",
    "</head>\r\n",
    "<body>\r\n",
    "\r\n",
    "    <p>Name: {{ name }}</p>\r\n",
    "\r\n",
    "</body>\r\n",
    "</html>\r\n",
    "```\r\n",
    "\r\n",
    "模板还有一个标记为`{{ name }}`的动态组件。双花括号{{}}中的 `name` 代表一个变量，其值将在呈现模板时指定。假设 `name` 的值是 `Jerry` ，那么在渲染模板之后，您将得到 ` <p>Name: Jerry</p> `"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "Flask 提供了一个函数，称为 render_template() ，用于呈现模板。正是这种功能使jinja与Flask相结合。要呈现一个模板，我们调用 render_template() ，其中包含模板名称以及作为关键字参数传递给模板的数据。函数的作用是: 渲染模板，并以字符串的形式返回 HTML。我们传递给模板的关键字参数称为 模板上下文(the context of the template) 或 简单的模板上下文(simply template context)。下面的清单显示了如何使用 `render_template()` 呈现索引 .html 模板。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "如果有很多参数需要传递给 render_template() ，那么不要使用逗号(,)分隔它们，而是创建一个字典并在其上应用 ** 操作符，以便将关键字参数传递给函数。例如:\r\n",
    "```\r\n",
    "@app.route('/')\r\n",
    "def index():\r\n",
    "    name, age, profession = \"Jerry\", 24, 'Programmer'\r\n",
    "    template_context = dict(name=name, age=age , profession=profession)\r\n",
    "    return render_template('index.html', **template_context)\r\n",
    "```\r\n",
    "索引模板现在可以访问三个模板变量: 名称、年龄和职业。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 在控制台中渲染模板\r\n",
    "\r\n",
    "在控制台中测试渲染模板非常简单，而不需要创建多个文件。从 jinja2包启动 Python shell 并导入 Template 类，如下所示。\r\n",
    "\r\n",
    "要创建 Template 对象，只需将模板的内容作为原始字符串传递。\r\n",
    "\r\n",
    "呈现 Template 对象的模板调用 render ()方法以及作为关键字参数的数据。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "source": [
    "from jinja2 import Template\r\n",
    "t = Template(\"Name: {{ name }}\")\r\n",
    "t.render(name='Jerry')"
   ],
   "outputs": [],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "# Jinja Template Language 金贾Jinja模板语言基础\r\n",
    "\r\n",
    "Jinja 模板语言是一个小的构造集合，它可以帮助我们自动创建模板。\r\n",
    "\r\n",
    "双花括号{{}}允许我们计算一个表达式、变量或函数调用并将结果输出到模板中:"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "source": [
    "from IPython.core.interactiveshell import InteractiveShell\r\n",
    "InteractiveShell.ast_node_interactivity = \"all\"  # 设置后 notebook 会显示每个变量的输出, 不然默认只显示最后一个变量的输出"
   ],
   "outputs": [],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "source": [
    "from jinja2 import Template\r\n",
    "Template(\"{{ 10 + 3 }}\").render()\r\n",
    "Template(\"{{ 10 - 3 }}\").render()\r\n",
    "Template(\"{{ 10 // 3 }}\").render()\r\n",
    "Template(\"{{ 10 / 3 }}\").render()\r\n",
    "Template(\"{{ 10 % 3 }}\").render()\r\n",
    "Template(\"{{ 10 ** 3 }}\").render()\r\n",
    "\r\n",
    "# 其他 Python 操作符，如 Comparision、 Logical 和 Membership 操作符也可以在表达式中使用。"
   ],
   "outputs": [],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "source": [
    "# Evaluating Variables\r\n",
    "Template(\"{{ var }}\").render(var=12)\r\n",
    "Template(\"{{ var }}\").render(var=\"hello\")"
   ],
   "outputs": [],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "我们不仅仅局限于数字和字符串，金贾模板还可以处理复杂的数据结构，如列表、字典、元组，甚至定制类。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "source": [
    "Template(\"{{ var[1] }}\").render(var=[1,2,3])\r\n",
    "Template(\"{{ var['profession'] }}\").render(var={'name':'tom', 'age': 25, 'profession': 'Manager' })\r\n",
    "Template(\"{{ var[2] }}\").render(var=(\"c\", \"c++\", \"python\"))\r\n",
    "\r\n",
    "class Foo:\r\n",
    "    def  __str__(self):\r\n",
    "        return \"This is an instance of Foo class\"\r\n",
    "Template(\"{{ var }}\").render(var=Foo())\r\n",
    "\r\n",
    "# 如果索引无效，金贾将默认输出一个空字符串。\r\n",
    "Template(\"{{ var[100] }}\").render(var=(\"c\", \"c++\", \"python\"))\r\n",
    "\r\n",
    "# 函数调用也一样\r\n",
    "def foo():\r\n",
    "    return \"foo() called\"\r\n",
    "\r\n",
    "Template(\"{{ foo() }}\").render(foo=foo)    "
   ],
   "outputs": [],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Attributes and Method 属性和方法\r\n",
    "\r\n",
    "若要访问对象的属性和方法，请使用点(.)运算符。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "source": [
    "class Foo:\r\n",
    "    def __init__(self, i):\r\n",
    "        self.i = i\r\n",
    "    def do_something(self):\r\n",
    "        return \"do_something() called\"\r\n",
    "\r\n",
    "Template(\"{{ obj.i }}\").render(obj=Foo(5))        \r\n",
    "\r\n",
    "Template(\"{{ obj.do_something() }}\").render(obj=Foo(5))"
   ],
   "outputs": [],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 注释的语法\r\n",
    "\r\n",
    "```\r\n",
    "{# comment in a line #}\r\n",
    "\r\n",
    "{#\r\n",
    "    comment expanded \r\n",
    "    to multiple\r\n",
    "    lines\r\n",
    "#}\r\n",
    "```"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 设置变量\r\n",
    "\r\n",
    "在模板中，我们可以使用 set 语句定义一个变量。\r\n",
    "\r\n",
    "```\r\n",
    "{% set fruit = 'apple' %}\r\n",
    "{% set name, age = 'Tom', 20 %} {# tuple unpacking works inside templates too #}\r\n",
    "```\r\n",
    "\r\n",
    "我们定义变量来存储某些复杂操作的结果，以便可以跨模板 重用 `reused`  它。控制结构之外定义的变量(在下面讨论)充当全局变量，可以在任何控制结构中访问。但是，在控制结构中创建的变量作为局部变量，并且只在定义它的控制结构中可见，这个规则的唯一例外是 if 语句。\r\n",
    "\r\n",
    "## 控制结构\r\n",
    "\r\n",
    "控制结构允许我们在模板中添加控制流和循环。默认情况下，控件结构使用`{% ...% }`分隔符，而不是双花括号 `{{ ... }}` 。\r\n",
    "\r\n",
    "### If 语句\r\n",
    "\r\n",
    "```\r\n",
    "{% if bookmarks %}\r\n",
    "    <p>User has some bookmarks</p>\r\n",
    "{% endif %}\r\n",
    "```\r\n",
    "在 Jinja  ，如果没有定义一个变量，那么它的结果就是 false。\r\n",
    "\r\n",
    "#### elif 和 else 子句:\r\n",
    "```\r\n",
    "{% if user.newbie %}\r\n",
    "    <p>Display newbie stages</p>\r\n",
    "{% elif user.pro %}\r\n",
    "    <p>Display pro stages</p>\r\n",
    "{% elif user.ninja %}\r\n",
    "    <p>Display ninja stages</p>\r\n",
    "{% else %}\r\n",
    "    <p>You have completed all stages</p>    \r\n",
    "{% endif %}\r\n",
    "```\r\n",
    "控制语句也可以嵌套。例如:\r\n",
    "```\r\n",
    "{% if user %}\r\n",
    "    {% if user.newbie %}\r\n",
    "        <p>Display newbie stages</p>\r\n",
    "    {% elif user.pro %}\r\n",
    "        <p>Display pro stages</p>\r\n",
    "    {% elif user.ninja %}\r\n",
    "        <p>Display ninja stages</p>\r\n",
    "    {% else %}\r\n",
    "        <p>You have completed all states</p>\r\n",
    "    {% endif %}\r\n",
    "{% else %}\r\n",
    "    <p>User is not defined</p>\r\n",
    "{% endif %}\r\n",
    "```\r\n",
    "在某些情况下，在一行中添加 if 语句非常有用。金贾支持 inline if 语句，但是如果表达式使用双花括号`{{ ... }}`而不是`{% ...% }`创建，则调用 inline if 语句。例如:\r\n",
    "```{{ \"User is logged in\" if loggedin else \"User is not logged in\"  }}```\r\n",
    "Else 子句是可选的，如果没有提供，则 else 块将被计算为未定义的对象。\r\n",
    "` {{ \"User is logged in\" if loggedin  }}`\r\n",
    "\r\n",
    "### 逻辑符号\r\n",
    "\r\n",
    "就像 Python 一样，我们可以在控制结构中使用 `Comparision` 、 `Logical` 和 `Membership` 操作符来创建更复杂的条件。以下是一些例子:\r\n",
    "\r\n",
    "```html\r\n",
    "{# if user.count is equal to 1000, '<p>User count is 1000</p>' will be printed #} # 这行是注释\r\n",
    "{% if users.count == 1000 %}\r\n",
    "    <p>User count is 1000</p>\r\n",
    "{%  endif %}\r\n",
    "\r\n",
    "\r\n",
    "{# expression 10 >= 2 is true so '<p>10 >= 2</p>' will be printed #}\r\n",
    "{% if 10 >= 2 %}\r\n",
    "    <p>10 >= 2</p>\r\n",
    "{%  endif %}\r\n",
    "\r\n",
    "\r\n",
    "{# expression \"car\" <= \"train\" is true so '<p>car <= train</p>' will be printed #}\r\n",
    "{% if \"car\" <= \"train\" %}\r\n",
    "    <p>car <= train</p>\r\n",
    "{%  endif %}\r\n",
    "\r\n",
    "\r\n",
    "{# \r\n",
    "    if user is logged in and is a superuser, \r\n",
    "    '<p>User is logged in and is a superuser</p>' will be printed \r\n",
    "#}\r\n",
    "{% if user.loggedin and user.is_superuser %}\r\n",
    "    <p>User is logged in and is a superuser</p>\r\n",
    "{% endif %}\r\n",
    "\r\n",
    "\r\n",
    "{# \r\n",
    "    if user is superuser or moderator or author \r\n",
    "    '<a href=\"#\">Edit</a>' will be printed \r\n",
    "#}\r\n",
    "{% if user.is_superuser or user.is_moderator or user.is_author %}\r\n",
    "    <a href=\"#\">Edit</a>\r\n",
    "{%  endif %}\r\n",
    "\r\n",
    "\r\n",
    "{# \r\n",
    "    if user and current_user points to the same object\r\n",
    "    <p>user and current_user are same</p> will be printed \r\n",
    "#}\r\n",
    "{% if user is current_user  %}\r\n",
    "    <p>user and current_user are same</p>\r\n",
    "{%  endif %}\r\n",
    "\r\n",
    "\r\n",
    "{# \r\n",
    "    As \"Flask\" is one of element in dictionary\r\n",
    "    '<p>Flask is in the dictionary</p>' will be printed \r\n",
    "#}\r\n",
    "{% if [\"Flask\"] in [\"Django\", \"web2py\", \"Flask\"] %}\r\n",
    "    <p>Flask is in the dictionary</p>\r\n",
    "{%  endif %}\r\n",
    "```\r\n",
    "\r\n",
    "如果你的条件变得太复杂，或者你只是想改变运算符的优先级，你可以把你的表达式包装在括号内如下:\r\n",
    "\r\n",
    "```\r\n",
    "{% if (user.marks > 80) and (user.marks < 90)  %}\r\n",
    "    <p>You grade is B</p>\r\n",
    "{%  endif %}\r\n",
    "```"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## For loop 循环\r\n",
    "\r\n",
    "循环允许我们迭代一个序列。例如:\r\n",
    "\r\n",
    "### 在list上迭代\r\n",
    "```\r\n",
    "{% set user_list = ['tom','jerry', 'spike'] %}\r\n",
    "\r\n",
    "<ul>\r\n",
    "{% for user in user_list %}\r\n",
    "    <li>{{ user }}</li>\r\n",
    "{% endfor %}\r\n",
    "</ul>\r\n",
    "```\r\n",
    "\r\n",
    "### 在字典上迭代\r\n",
    "```\r\n",
    "{% set employee = { 'name': 'tom', 'age': 25, 'designation': 'Manager' } %}\r\n",
    "\r\n",
    "<ul>\r\n",
    "{% for key in employee.items() %}   \r\n",
    "    这里可能为keys()\r\n",
    "    <li>{{ key }} : {{ employee[key] }}</li>\r\n",
    "{% endfor %}\r\n",
    "</ul>\r\n",
    "```\r\n",
    "\r\n",
    "```\r\n",
    "{% set employee = { 'name': 'tom', 'age': 25, 'designation': 'Manager' } %}\r\n",
    "\r\n",
    "<ul>\r\n",
    "{% for key, value in employee.items() %}\r\n",
    "    <li>{{ key }} : {{ value }}</li>\r\n",
    "{% endfor %}\r\n",
    "</ul>\r\n",
    "```\r\n",
    "### For ... else 子句\r\n",
    "\r\n",
    "For 循环也可以像 Python 一样接受一个可选的 else 子句，但是它的用法略有不同。\r\n",
    "\r\n",
    "回想一下，在 `Python` 中，当 for 循环后面跟着 else 子句时，只有当 for 循环在序列上循环后终止或序列为空时，才会执行 else 子句。当 for 循环被 break 语句终止时，不会执行它。\r\n",
    "在 `jinja` 中, 当 else 子句与 for 循环一起使用时，它只在序列为空或未定义时执行。例如:\r\n",
    "\r\n",
    "```\r\n",
    "{% set user_list = [] %}\r\n",
    "\r\n",
    "<ul>\r\n",
    "{% for user in user_list  %}\r\n",
    "    <li>{{ user }}</li>\r\n",
    "{% else %}\r\n",
    "    <li>user_list is empty</li>\r\n",
    "{% endfor %}\r\n",
    "</ul>\r\n",
    "```\r\n",
    "\r\n",
    "### loop 的特殊变量\r\n",
    "\r\n",
    "For 循环使您可以访问一个称为 loop 的特殊变量，以跟踪循环的进度。例如:\r\n",
    "\r\n",
    "```\r\n",
    "<ul>\r\n",
    "{% for user in user_list  %}\r\n",
    "    <li>{{ loop.index }} - {{ user }}</li>\r\n",
    "{% endfor %}\r\n",
    "</ul>\r\n",
    "```\r\n",
    "\r\n",
    "For 循环中的 loop.index 返回从1开始的循环的当前迭代。下表列出了循环变量的其他常用属性。\r\n",
    "\r\n",
    "| Variable         | Description                                                  |\r\n",
    "| :--------------- | :----------------------------------------------------------- |\r\n",
    "| `loop.index0`    | same as `loop.index` but 0 indexed i.e it starts counting iteration from 0 instead of 1. |\r\n",
    "| `loop.revindex`  | returns the iteration from the end of the loop (1 indexed).  |\r\n",
    "| `loop.revindex0` | same as `loop.revindex` but 0 indexed.                       |\r\n",
    "| `loop.first`     | returns `True` if the current iteration is the first iteration. Otherwise `False`. |\r\n",
    "| `loop.last`      | returns `True` if the current iteration is the last iteration. Otherwise `False`. |\r\n",
    "| `loop.length`    | returns the length of the sequence the for loop is iterating over. |\r\n",
    "| `loop.index0`    | 和 `loop.index` 一样 但是0索引, 即开始计算从0开始的迭代而不是1 |\r\n",
    "| `loop.revindex`  | 返回从循环末尾开始的迭代(1个索引)                  |\r\n",
    "| `loop.revindex0` | 和 `loop.revindex` 一样 但是0索引                               |\r\n",
    "| `loop.first`     | 返回 `True` 如果当前迭代是第一次迭代，否则`False`.          |\r\n",
    "| `loop.last`      | 返回 `True` 如果当前迭代是最后一次迭代，否则`False`.        |\r\n",
    "| `loop.length`    | 返回 for 循环遍历的序列的长度                      |\r\n",
    "\r\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Filters 过滤器\r\n",
    "\r\n",
    "过滤器在变量呈现之前修改它们。使用过滤器的语法如下:\r\n",
    "\r\n",
    "`variable_or_value|filter_name`\r\n",
    "\r\n",
    "左边为变量, 右边为一个filter的名字\r\n",
    "\r\n",
    "下面是一个例子:\r\n",
    "\r\n",
    "`{{ comment|title }}`  # `title` 过滤器 将每个单词的第一个字符大写，因此如果变量 `comment` 设置为 “dust in the wind”，那么输出将为“ Dust In The Wind”。\r\n",
    "\r\n",
    "我们还可以 链接调用(chain) 多个过滤器 来修改输出。例如:\r\n",
    "\r\n",
    "`{{ full_name|striptags|title }}`  # `Striptags` 过滤器从变量中删除所有 HTML 标记。在上面的代码中，striptags 过滤器将首先应用，然后是 `title` 过滤器。\r\n",
    "\r\n",
    "有些过滤器也可以接受参数。为了将参数传递给过滤器，可以像调用函数一样调用它。例如:\r\n",
    "\r\n",
    "`{{ number|round(2) }}`  # 舍入过滤器将数字舍入到指定的位数(这里是2位小数)。\r\n",
    "\r\n",
    "下表列出了一些常用的过滤器。\r\n",
    "\r\n",
    "| Name 姓名    | Description 描述                                             |\r\n",
    "| :----------- | :----------------------------------------------------------- |\r\n",
    "| `upper`      | converts all the characters to uppercase. 将所有字符转换为大写 |\r\n",
    "| `lower`      | converts all the characters to lowercase. 将所有字符转换成小写 |\r\n",
    "| `capitalize` | capitalizes the first character and converts rest of the characters to lowercase. 将第一个字符大写，并将其余字符转换为小写 |\r\n",
    "| `escape`     | escapes the value 逃避价值                                   |\r\n",
    "| `safe`       | prevents escaping 防止逃跑                                   |\r\n",
    "| `length`     | returns the number of elements in the sequence 返回序列中元素的数量 |\r\n",
    "| `trim`       | removes leading and trailing whitespace characters 删除前导和尾随空格字符 |\r\n",
    "| `random`     | returns a random item from the sequence 从序列中返回一个随机项 |\r\n",
    "\r\n",
    "注意: 内置过滤器的完整列表可以在这里找到。\r\n",
    "\r\n",
    "https://jinja.palletsprojects.com/en/3.0.x/templates/#list-of-builtin-filters\r\n",
    "\r\n",
    "\r\n",
    "| [`abs()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.abs) | [`float()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.float) | [`lower()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.lower) | [`round()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.round) | [`tojson()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.tojson) |\r\n",
    "| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |\r\n",
    "| [`attr()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.attr) | [`forceescape()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.forceescape) | [`map()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.map) | [`safe()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.safe) | [`trim()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.trim) |\r\n",
    "| [`batch()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.batch) | [`format()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.format) | [`max()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.max) | [`select()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.select) | [`truncate()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.truncate) |\r\n",
    "| [`capitalize()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.capitalize) | [`groupby()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.groupby) | [`min()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.min) | [`selectattr()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.selectattr) | [`unique()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.unique) |\r\n",
    "| [`center()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.center) | [`indent()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.indent) | [`pprint()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.pprint) | [`slice()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.slice) | [`upper()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.upper) |\r\n",
    "| [`default()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.default) | [`int()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.int) | [`random()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.random) | [`sort()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.sort) | [`urlencode()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.urlencode) |\r\n",
    "| [`dictsort()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.dictsort) | [`join()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.join) | [`reject()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.reject) | [`string()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.string) | [`urlize()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.urlize) |\r\n",
    "| [`escape()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.escape) | [`last()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.last) | [`rejectattr()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.rejectattr) | [`striptags()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.striptags) | [`wordcount()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.wordcount) |\r\n",
    "| [`filesizeformat()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.filesizeformat) | [`length()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.length) | [`replace()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.replace) | [`sum()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.sum) | [`wordwrap()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.wordwrap) |\r\n",
    "| [`first()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.first) | [`list()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.list) | [`reverse()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.reverse) | [`title()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.title) | [`xmlattr()`](https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.xmlattr) |\r\n",
    "\r\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Macros 宏\r\n",
    "\r\n",
    "Jinjia的宏类似于 Python 中的函数。这个想法是通过给代码命名来创建可重用(reusable)的代码。例如:\r\n",
    "\r\n",
    "```jinja2\r\n",
    "{% macro render_posts(field, sep=False) %}  {# 注意为 单大括号}\r\n",
    "    <div>\r\n",
    "        {% for post in post_list %} \r\n",
    "            <h2>{{ post.title }}</h2> \r\n",
    "            <article>\r\n",
    "                {{ post.html|safe }} {# 注意为 双大括号}\r\n",
    "            </article>\r\n",
    "        {% endfor %}\r\n",
    "        {% if sep %}<hr>{% endif %} {# 注意为 单大括号}\r\n",
    "    </div>\r\n",
    "{% endmacro %} # 注意为 单大括号\r\n",
    "```\r\n",
    "\r\n",
    "这里我们创建了一个名为 render _ posts 的宏，它接受一个名为 post _ list 的必需参数和一个名为 sep. 的可选参数。要使用宏调用它，请如下:\r\n",
    "\r\n",
    "`{{ render_posts(posts) }}`\r\n",
    "\r\n",
    "宏定义必须在使用之前出现，否则会出现错误。\r\n",
    "\r\n",
    "与其在模板中添加宏，不如将它们存储在单独的文件中，然后根据需要导入文件。\r\n",
    "\r\n",
    "假设我们已经将所有宏存储在模板目录中名为宏的文件中。现在要从宏导入宏，我们使用 import 语句如下:\r\n",
    "\r\n",
    "`{% import \"macros.html\" as macros %}`  # 注意为 双大括号\r\n",
    "\r\n",
    "我们现在可以使用宏变量引用在宏中定义的宏。例如:\r\n",
    "\r\n",
    "`{{ macros.render_posts(posts) }}`      # 注意为 双大括号\r\n",
    "\r\n",
    "Import 语句 `{% import \"macros.html\" as macros %}` 将 `macros.html` (在顶级定义)中的所有宏和变量导入到模板中。\r\n",
    "\r\n",
    "或者，我们也可以使用 Import 语句在模板中 `只导入部分` 选定的名称，如下所示:\r\n",
    "\r\n",
    "{% from \"macros.html\" import render_posts  %}\r\n",
    "\r\n",
    "在使用宏时，您会遇到需要向宏传递任意数量的参数的情况。\r\n",
    "\r\n",
    "\r\n",
    "类似于 Python 中的 *args 和 **kwargs，在宏中您可以访问 varargs 和 kwargs。\r\n",
    "\r\n",
    "Varargs: 它捕获作为元组(tuple)传递给宏的附加位置参数。 如: (200,400)\r\n",
    "\r\n",
    "Kwargs: 它捕获作为字典(dictionary)传递给宏的附加关键字参数。如: {'width':200,'heigh':400}\r\n",
    "\r\n",
    "虽然您可以访问宏中的这两个变量，但是不需要在宏标头中显式声明它们。下面是一个例子:\r\n",
    "\r\n",
    "```jinja2\r\n",
    "{% macro custom_renderer(para) %}  {# 单大括号 #}\r\n",
    "    <p>{{ para }}</p>   {# 双大括号 #}\r\n",
    "    <p>varargs: {{ varargs }}</p>  \r\n",
    "    <p>kwargs: {{ kwargs }}</p>\r\n",
    "{% endmacro %}\r\n",
    "\r\n",
    "{{ custom_renderer(10, \"apple\", name='spike', age=15) }}   # 注意为 双大括号\r\n",
    "```\r\n",
    "\r\n",
    "在这种情况下，将额外的位置参数(如 \"apple\" )分配给 varargs，并将额外的关键字参数(name='spike', age=15)分配给 kwargs。\r\n",
    "注: 这里的 10 直接分配给了 para (做为固定参数)\r\n",
    "\r\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Escaping 转义\r\n",
    "\r\n",
    "默认情况下，出于安全考虑，金贾会自动转义变量并输出。因此，如果一个变量有一个包含 HTML 的字符串，比如 `\"<p>Escaping in Jinja</p>\"` ，那么它将被显示为 `\"<p>&lt;p&gt;Escaping in Jinja&lt;/p&gt;</p>\"` 。这将导致 HTML 标记在浏览器中显示，而不是解释。如果您知道数据是安全的，并且希望按原样呈现而不想转义，那么可以使用 `safe` (安全) `filter` 过滤器。例如:\r\n",
    "\r\n",
    "```jinja2\r\n",
    "{% set html = <p>Escaping in Jinja</p> %}\r\n",
    "{{ html|safe }}  # safe 为一个过滤器,参考前2节的filter\r\n",
    "```\r\n",
    "\r\n",
    "然而, 重复地对大块内容应用 `safe` 过滤器可能既笨拙又不方便，这就是为什么`金贾`提供了 `autoescape` 语句来 转义 或 不转义 (escape or unescape ) 大块内容。它接受 `true` 或 `false` 作为参数，分别打开和关闭自动转义。例如:\r\n",
    "\r\n",
    "```jinja2\r\n",
    "{% autoescape true %}\r\n",
    "    Escaping enabled\r\n",
    "{% endautoescape %}\r\n",
    "\r\n",
    "{% autoescape false %}\r\n",
    "    Escaping disabled\r\n",
    "{% endautoescape %}\r\n",
    "```\r\n",
    "\r\n",
    "在 `{%autoescape false% } 和 {%endautoescape%}` 之间的所有内容将按原样呈现而不进行转义。如果您想在关闭自动转义时转义某些元素，请使用 `escape`  过滤器。例如:\r\n",
    "\r\n",
    "```jinja2\r\n",
    "{% autoescape false %}\r\n",
    "    <div class=\"post\">\r\n",
    "        {% for post in post_list %}\r\n",
    "            <h2>{{ post.title }}</h2>\r\n",
    "            <article>\r\n",
    "                {{ post.html }}\r\n",
    "            </article>\r\n",
    "        {% endfor %}        \r\n",
    "    </div>\r\n",
    "    <div>\r\n",
    "        {% for comment in comment_list %}\r\n",
    "            <p>{{ comment|escape }}</p>   # 这里`escape`转义过滤器已打开 \r\n",
    "        {% endfor %}\r\n",
    "    </div>\r\n",
    "{% endautoescape %}\r\n",
    "\r\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Including templates 包括模板\r\n",
    "\r\n",
    "`Include` 语句在另一个模板中呈现模板。 `Include` 语句通常用于渲染整个站点重复的静态部分。下面是 `include` 语句的语法:\r\n",
    "\r\n",
    "`{% include 'path/to/template' %}`\r\n",
    "\r\n",
    "假设我们有一个简单的导航栏存储在模板 `templates`  目录的 `nav.html` 文件中，如下所示:\r\n",
    "\r\n",
    "```html\r\n",
    "<nav>\r\n",
    "    <a href=\"/home\">Home</a>\r\n",
    "    <a href=\"/blog\">Blog</a>\r\n",
    "    <a href=\"/contact\">Contact</a>  \r\n",
    "</nav>\r\n",
    "```\r\n",
    "\r\n",
    "为了将导航条包含在 home. html 中，我们使用以下代码:\r\n",
    "```html\r\n",
    "<!DOCTYPE html>\r\n",
    "<html lang=\"en\">\r\n",
    "<head>\r\n",
    "    <meta charset=\"UTF-8\">\r\n",
    "    <title>Title</title>\r\n",
    "</head>\r\n",
    "<body>\r\n",
    "\r\n",
    "    {# including the navigation bar from nav.html #}\r\n",
    "    {% include 'nav.html' %}    {# 单大括号 #}\r\n",
    "\r\n",
    "</body>\r\n",
    "</html>\r\n",
    "```\r\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Template Inheritance 模板继承\r\n",
    "\r\n",
    "模板继承是 Jinja Templating 最强大的一个方面。模板继承背后的思想有点类似于面向对象编程。\r\n",
    "我们首先创建一个基本模板，其中包含HTML骨架(skeleton)和子模板可以覆盖的标记(markers)。标记是使用 `block` 语句创建的。子模板使用 `extends` 语句来继承或扩展基模板。让我们举个例子:\r\n",
    "\r\n",
    "```html\r\n",
    "{# This is templates/base.html template #}\r\n",
    "<!DOCTYPE html>\r\n",
    "<html lang=\"en\">\r\n",
    "<head>\r\n",
    "    <meta charset=\"UTF-8\">\r\n",
    "    <title>{% block title %}Default Title{% endblock %}</title>\r\n",
    "</head>\r\n",
    "<body>\r\n",
    "\r\n",
    "    {% block nav %}\r\n",
    "        <ul>\r\n",
    "            <li><a href=\"/home\">Home</a></li>\r\n",
    "            <li><a href=\"/api\">API</a></li>\r\n",
    "        </ul>\r\n",
    "    {% endblock %}\r\n",
    "\r\n",
    "    {% block content %}\r\n",
    "\r\n",
    "    {% endblock %}\r\n",
    "\r\n",
    "</body>\r\n",
    "</html>\r\n",
    "```\r\n",
    "\r\n",
    "这是我们的基本模板页 `base.html` 。它使用子模板可以填充的 `block` 语句定义了三个块。 `block` 语句带了一个参数，这个参数是块的名称。在模板内部，块名称必须是唯一的，否则会出现错误。\r\n",
    "\r\n",
    "子模板是扩展基本模板的模板。子模板可以添加、重写或保留父块的内容。下面是我们如何创建一个子模板。\r\n",
    "\r\n",
    "```html\r\n",
    "{# this is templates/child.html template #}\r\n",
    "{% extends 'base.html' %}  # 扩展自'base.html' \r\n",
    "\r\n",
    "{% block content %}\r\n",
    "    {% for bookmark in bookmarks %}\r\n",
    "        <p>{{ bookmark.title }}</p>\r\n",
    "    {% endfor %}\r\n",
    "{% endblock %}\r\n",
    "```\r\n",
    "\r\n",
    "`Extends` 语句告诉Jinja `child.html` 是一个子模板并继承自 `base.html` 。当金贾遇到 extends 语句时，它加载基模板, 这里是`base.html`，然后用子模板中相同名称的内容块替换父模板中的内容块。如果在子模板中没有找到匹配的名称块，那么将使用来自父模板的块。\r\n",
    "\r\n",
    "请注意，在子模板中，我们只覆盖 `content` 块，因此在呈现子模板时将使用父模板的 `title` 和 `nav` 的默认内容。输出应该是这样的:\r\n",
    "\r\n",
    "```html\r\n",
    "<head>\r\n",
    "    <meta charset=\"UTF-8\">\r\n",
    "    <title>Default Title</title>\r\n",
    "</head>\r\n",
    "<body>\r\n",
    "    \r\n",
    "    <ul>\r\n",
    "        <li><a href=\"/home\">Home</a></li>\r\n",
    "        <li><a href=\"/api\">API</a></li>\r\n",
    "    </ul>\r\n",
    "\r\n",
    "    <p>Bookmark title 1</p>\r\n",
    "    <p>Bookmark title 2</p>\r\n",
    "    <p>Bookmark title 3</p>\r\n",
    "    <p>Bookmark title 4</p>\r\n",
    "\r\n",
    "\r\n",
    "</body>\r\n",
    "</html>\r\n",
    "```\r\n",
    "\r\n",
    "如果我们愿意，我们可以通过覆盖 child. html 中的 title 块来更改默认的 title，如下所示:\r\n",
    "\r\n",
    "```\r\n",
    "{# this is templates/child.html template #}\r\n",
    "{% extends 'base.html' %}\r\n",
    "\r\n",
    "{% block title %}  {# 关注这段 #}\r\n",
    "    Child Title \r\n",
    "{% endblock %}\r\n",
    "\r\n",
    "{% block content %}\r\n",
    "    {% for bookmark in bookmarks %}\r\n",
    "        <p>{{ bookmark.title }}</p>\r\n",
    "    {% endfor %}\r\n",
    "{% endblock %}\r\n",
    "```\r\n",
    "\r\n",
    "在重写一个块之后，您仍然可以通过调用 `super()` 函数来引用父模板中的内容。 `Super()` 调用通常用于子模板需要在来自父模板的内容之外再增加自己的内容时。\r\n",
    "\r\n",
    "例如:\r\n",
    "\r\n",
    "```jinja2\r\n",
    "{# this is templates/child.html template #}\r\n",
    "{% extends 'base.html' %}\r\n",
    "\r\n",
    "{% block title %}\r\n",
    "    Child Title \r\n",
    "{% endblock %}\r\n",
    "\r\n",
    "{% block nav %} \r\n",
    "    {{ super() }} {# referring to the content in the parent templates 引用父模板中的内容 #}\r\n",
    "    <li><a href=\"/contact\">Contact</a></li>\r\n",
    "    <li><a href=\"/career\">Career</a></li>\r\n",
    "{% endblock %}\r\n",
    "\r\n",
    "{% block content %}\r\n",
    "    {% for bookmark in bookmarks %}\r\n",
    "        <p>{{ bookmark.title }}</p>\r\n",
    "    {% endfor %}\r\n",
    "{% endblock %}\r\n",
    "```\r\n",
    "\r\n",
    "输出:\r\n",
    "```html\r\n",
    "<head>\r\n",
    "    <meta charset=\"UTF-8\">\r\n",
    "    <title>Child Title</title>\r\n",
    "</head>\r\n",
    "<body>\r\n",
    "    \r\n",
    "    <ul>\r\n",
    "        <li><a href=\"/home\">Home</a></li>\r\n",
    "        <li><a href=\"/api\">API</a></li>\r\n",
    "        <li><a href=\"/contact\">Contact</a></li>\r\n",
    "        <li><a href=\"/career\">Career</a></li>\r\n",
    "    </ul>\r\n",
    "\r\n",
    "    <p>Bookmark title 1</p>\r\n",
    "    <p>Bookmark title 2</p>\r\n",
    "    <p>Bookmark title 3</p>\r\n",
    "    <p>Bookmark title 4</p>\r\n",
    "\r\n",
    "\r\n",
    "</body>\r\n",
    "</html>\r\n",
    "```\r\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "这就是你需要知道的关于jinja模板的一切。在接下来的课程中，我们将使用这些知识来创建一些很棒的模板。"
   ],
   "metadata": {}
  }
 ],
 "metadata": {
  "orig_nbformat": 4,
  "language_info": {
   "name": "python",
   "version": "3.9.6",
   "mimetype": "text/x-python",
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "pygments_lexer": "ipython3",
   "nbconvert_exporter": "python",
   "file_extension": ".py"
  },
  "kernelspec": {
   "name": "python3",
   "display_name": "Python 3.9.6 64-bit"
  },
  "interpreter": {
   "hash": "c644d696b95f5e0f4df3c6556741cf30bcf9ea6ca93c3e1f29fcf31d885534fc"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}